/*Author: Rony Lahav (ID:043489889)*/

import java.io.File;
import java.io.IOException;
import java.net.DatagramSocket;
import java.net.ServerSocket;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Random;

/**
 * Main class of the project - initializes vars and runs a peer
 */
public class final_project {
	
	public static String configFilePath=null;
	public static String outputFilePath=null;
	
	private static DBTuple myTuple = null;
	private static DBTuple myMainSN = null;
	public static int frequency;
	private static final_project peer = null;
	public static DatagramSocket mainUDPSoc=null;
	public static ServerSocket mainTCPServerSoc=null;
	public static int mainTCPSoc=0;
	
	public static Thread tcpListenerThread=null;
	public static Thread udpListenerThread=null;
	public static Thread ATManagerThread=null;
	public static Thread AVManagerThread=null;
	public static Thread StatusManagerThread=null;
	public static Thread CmdManagerThread=null;
	
	private static long threadUniqueId=0;
	public static HashMap<Long, Thread> livingThreads = new HashMap<Long,Thread>();
	
	public static boolean shutDown = false;
	public static boolean inFallBackMode = false;
	public static boolean amIanSN = true;
	public static int timeout;
	public static int maxBufferSize=10240;	//10KB
	
	public static LinkedList<ICmd> commandsList;
	public static HashMap<String, byte[]> loadedFiles = new HashMap<String, byte[]>();
	
	public static String currentDir = null;
	
	public static long idleTime=0;
	public static long idleStartTime=0;
	
	private final_project(){
	}
	
	//singleton
	public static final_project getInstance()
	{
		if (peer == null) {
			peer = new final_project();
		}
		return peer;
	}
	
	public static String getMyPeerID() {
		return myTuple.getPeerID();
	}
	
	public static String getMyKey() {
		if (myTuple==null)
			return null;
		return myTuple.getKey();
	}
	
	public static DBTuple getMyMainSN(){
		return myMainSN;
	}
	
	private boolean initConfigData() throws IOException{
		DBTuple myTuple = ConfigFileManager.getInstance().processDataSection();
		if (myTuple==null)
			return false;
		this.myTuple=myTuple;
		commandsList=ConfigFileManager.getInstance().getCommands();
		return true;
	}
	
	public static void activateATThread(int[] intervals){
		ATManager manager = new ATManager(intervals);
		ATManagerThread=new Thread(manager);
		ATManagerThread.start();
	}
	
	public static void activateCMDThread(){
		CmdManager manager = new CmdManager();
		CmdManagerThread=new Thread(manager);
		CmdManagerThread.start();
	}
	
	private static void activateUDPThread(){
		UDPListener manager = new UDPListener(mainUDPSoc);
		udpListenerThread=new Thread(manager);
		udpListenerThread.start();
	}
	
	private static void activateTCPThread(){
		initMyTCPPort(false);
		boolean success=false;
		do {
			try {
				mainTCPServerSoc = new ServerSocket(mainTCPSoc);
				success=true;
			} catch (Exception e) {
				initMyTCPPort(true);
			}
		} while (!success && !final_project.isShutDown());
		TCPListener manager = new TCPListener(mainTCPServerSoc);
		tcpListenerThread=new Thread(manager);
		tcpListenerThread.start();
	}
		
	private static void activateAVThread(){
		AVManager manager = null;
		if (amIanSN)
			manager = new AVManagerSN();
		else if (!inFallBackMode)
			manager = new AVManagerReg();
		else
			manager = new AVManager();
		AVManagerThread=new Thread(manager);
		AVManagerThread.start();
	}
	
	private static void activateStatusThread(){
		StatusManager manager = null;
		if (amIanSN)
			manager = new StatusManagerSN();
		else if (!inFallBackMode)
			manager = new StatusManagerReg();
		else
			manager = new StatusManager();
		StatusManagerThread=new Thread(manager);
		StatusManagerThread.start();
	}
	
	public static boolean isShutDown() {
		return shutDown;
	}
	
	public static void removeThreadFromLivingList(long key){
		livingThreads.remove(key);
	}
	
	public static long getNewUniqueID(){
		++threadUniqueId;
		return threadUniqueId;
	}
	
	public static String getMyIP() {
		return myTuple.getPeerIP();
	}
	
	public static void addThreadToLivingList(long key, Thread thread){
		livingThreads.put(key, thread);
	}
	
	public static void goToFallBackMode(){
		inFallBackMode=true;
		activateAVThread();
		activateStatusThread();
	}
	
	public void start(){
		try{
			if (!initConfigData())
				shutDown=true;
			else{
				// Try to listen for incoming UDP connection
				try {
					if (mainUDPSoc == null){
						mainUDPSoc = new DatagramSocket(getMyUDPPort());
					}
				} catch (Exception e) {	//exception that doesn't allow program continuation
					shutDown=true;
				}
				if (shutDown==false){	//UDP listener socket is active
					try {
						File curDir = new File(".");
						currentDir=curDir.getCanonicalPath();
					} catch (Exception e) {	//exception that doesn't allow program continuation
						shutDown=true;
					}
					try {
						if(!final_project.isShutDown()){
							activateTCPThread();
							activateAVThread();
							activateUDPThread();
							if (amIanSN)
								SNsHandler.getInstance().firstConnectionToSNs();
							else if (!inFallBackMode){
								long id = getNewUniqueID();
								MainSNLocator locateMainSN = new MainSNLocator(id);
								Thread thread = new Thread(locateMainSN);
								final_project.addThreadToLivingList(id, thread);
								thread.start();
							}
							activateCMDThread();
							activateStatusThread();
						}
					} catch (Exception e) {	//exception that doesn't allow program continuation
						shutDown=true;
					}
				}
				else{
					shutDown=true;
				}
			}
			do {
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {}
			} while (!final_project.isShutDown());
		}
		catch(Exception ex){}
		finally{
			while (tcpListenerThread != null && tcpListenerThread.isAlive()){
				tcpListenerThread.interrupt();
			}
			while (udpListenerThread != null && udpListenerThread.isAlive()){
				udpListenerThread.interrupt();
			}
			while (ATManagerThread != null && ATManagerThread.isAlive()){
				ATManagerThread.interrupt();
			}
			while (CmdManagerThread != null && CmdManagerThread.isAlive()){
				CmdManagerThread.interrupt();
			}
			while (AVManagerThread != null && AVManagerThread.isAlive()){
				AVManagerThread.interrupt();
			}
			while (StatusManagerThread != null && StatusManagerThread.isAlive()){
				StatusManagerThread.interrupt();
			}
			Logger.getInstance().close();
		}
		
	}
	
	public static int getMyUDPPort() {
		return Integer.parseInt(myTuple.getUdpPort());
	}
	
	public static DBTuple getMyTuple(){
		return myTuple;
	}
	
	public static int initMyTCPPort(boolean forceNewPort) {
		if (!forceNewPort){
			if (mainTCPSoc==0){
				Random rand = new Random(System.currentTimeMillis());
				mainTCPSoc=rand.nextInt(65537);
			}
		}
		else{
			Random rand = new Random(System.currentTimeMillis());
			mainTCPSoc=rand.nextInt(65537);
		}
		return (mainTCPSoc);
	}
	
	public static void updateIdleTimes(long newIdleTime){
		idleTime=newIdleTime;
		idleStartTime=System.currentTimeMillis();
	}
	
	public static void main(String[] args) {
		configFilePath = args[0];
		outputFilePath = "output.txt";
		System.setProperty("java.net.preferIPv4Stack", "true");
		
		final_project peer = final_project.getInstance();
		peer.start();
		
		System.exit(1);
	}

	public static void setMyMainSN(DBTuple myMainSN) {
		final_project.myMainSN = myMainSN;
	}
	
	public static DBTuple getMyMainSN(DBTuple myMainSN) {
		return myMainSN;
	}


}
